<?php


namespace libraries;

use libraries\exception\FileDownloadException;
use libraries\exception\FileUploadException;
use think\facade\Request;

/**
 * 系统通用工具
 * Class Tools
 * @package libraries
 */
class Tools
{

    /**
     * 获取当前系统时间(精确到毫秒)
     * @return float
     */
    static function getMillisecond() {
        list($t1, $t2) = explode(' ', microtime());
        //return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
    }

    /**
     * 获取当前系统时间(精确到微秒)
     * @return float
     */
    static function getMicrosecond() {
        list($t1, $t2) = explode(' ', microtime());
        //return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000000);
        return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000000);
    }


    /**
     * desc：API接口专用[可改造]
     * author：wh
     * @param int $code
     * @param string $message
     * @param array $data
     * @return array
     */
    //static function setResult(int $code, $message='', array $data=[]){
    //    $data_packet = [
    //        'header'=>[
    //            'code'=>$code,
    //            'message'=>$message,
    //            'token'=>Tools::rand_str(32, 5),
    //        ],
    //        'data'=>$data
    //    ];
    //    return $data_packet;
    //}

    /**
     * desc：一般性通用返回结果封装
     * author：wh
     * @param $code
     * @param string $message 错误消息
     * @param array $data 返回数据
     * @param bool $is_log 是否写入日志
     * @return array
     */
    static function set_result($code, $message='', $data=[], $is_log=false){
        $data_packet = [
            'code'=>$code,
            'msg'=>$message,
            'data'=>$data,
            //'token'=>Tools::rand_str(32, 5),
        ];
        //默认写入日志
        //coding
        if($is_log){

        }

        //新项目可以修改为
        //if(Request::isAjax()) return json($data_packet);
        return $data_packet;
    }
    /**
     * desc：API专用返回格式
     * author：wh
     * @param array $code 传入的是错误码数组（SystemErrors.php）
     * @param string $message
     * @param array $data
     * @return array
     */
    static function set_api_result(array $error_code_arr, array $data=[]){
        $data_packet = [
            'code'=>$error_code_arr[0],
            'msg'=>$error_code_arr[1],
            'data'=>$data,
            'token'=>Tools::rand_str(32, 5),
        ];
        return $data_packet;
    }

    /**
     * desc：
     * author：wh
     * @param int $code
     * @param string $message
     * @param array $data
     * @return array
     */
    static function set_result_msg(int $code, $message='', array $data=[]){
        $data_packet = [
            'code'=>$code,
            'msg'=>$message,
            'data'=>$data,
        ];
        return $data_packet;
    }


    /**
     * UTF8字符串加密
     * @param string $string
     * @return string
     */
    static function encode($string)
    {
        list($chars, $length) = ['', strlen($string = iconv('utf-8', 'gbk', $string))];
        for ($i = 0; $i < $length; $i++) {
            $chars .= str_pad(base_convert(ord($string[$i]), 10, 36), 2, 0, 0);
        }
        return $chars;
    }

    /**
     * UTF8字符串解密
     * @param string $string
     * @return string
     */
    static function decode($string)
    {
        $chars = '';
        foreach (str_split($string, 2) as $char) {
            $chars .= chr(intval(base_convert($char, 36, 10)));
        }
        return iconv('gbk', 'utf-8', $chars);
    }
    /**
     * desc：换行输出（一般用于调试 eg：在循环中输出）
     * author：wh
     */
    static function br_echo($msg){
        echo '<br/>';
        echo $msg;
        echo '<br/>';
    }


    /**
     * 输出xml字符
     * @throws
     **/
    static function to_xml($data){
        if(!is_array($data) || count($data) <= 0){
            throw new \Exception("数组数据异常！");
        }

        $xml = "<xml>";
        foreach ($data as $key=>$val){
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    /**
     * desc：返回xml 对象
     * author：wh
     * @param bool $res
     * @return string
     */
    static function xml_res_return($res = true){
        $tmp_suc = $res?'SUCCESS':'FAIL';
        $tmp_ok = $res?'OK':'ERR';
        $xml = "<xml>
              <return_code><![CDATA[{$tmp_suc}]]></return_code>
              <return_msg><![CDATA[{$tmp_ok}]]></return_msg>
            </xml>";

        return simplexml_load_string($xml);
    }

    /**
     * XML编码
     * @param mixed $data 数据
     * @param string $encoding 数据编码
     * @param string $root 根节点名
     * @return string
     */
    static function xml_encode($data, $encoding='utf-8', $root='xml') {
        $xml    = '<?xml version="1.0" encoding="' . $encoding . '"?>';
        $xml   .= '<' . $root . '>';
        $xml   .= self::data_to_xml($data);
        $xml   .= '</' . $root . '>';
        return $xml;
    }

    /**
     * 数据XML编码
     * @param mixed $data 数据
     * @return string
     */
    static function data_to_xml($data) {
        $xml = '';
        foreach ($data as $key => $val) {
            is_numeric($key) && $key = "item id=\"$key\"";
            $xml    .=  "<$key>";
            $xml    .=  ( is_array($val) || is_object($val)) ? self::data_to_xml($val) : $val;
            list($key, ) = explode(' ', $key);
            $xml    .=  "</$key>";
        }
        return $xml;
    }
    /**
     * 生成随机字符串
     * @param int $length 生成长度
     * @param int $type 生成类型：0-小写字母+数字，1-小写字母，2-大写字母，
     * 3-数字，4-小写+大写字母，5-小写+大写+数字
     * @return string
     */
    static function rand_str($length = 8, $type = 0) {
        $a = 'abcdefghijklmnopqrstuvwxyz';
        $A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $n = '0123456789';

        switch ($type) {
            case 1: $chars = $a; break;
            case 2: $chars = $A; break;
            case 3: $chars = $n; break;
            case 4: $chars = $a.$A; break;
            case 5: $chars = $a.$A.$n; break;
            default: $chars = $a.$n;
        }

        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[ mt_rand(0, strlen($chars) - 1) ];
        }
        return $str;
    }

    /**
     * desc：php二维数组排序(升、降)
     * author：wh
     * @param $data 数据源(必须是二维数组)
     * @param $field 要排序的字段(必须) eg：年龄或者价格
     * @param bool $sort 排序方式
     * @param bool $unique_field 指定唯一字段 eg：例如userID一般都是唯一的
     * @return array 返回排序后的数据源
     */
    static function arr_arr_to_sort($data, $field, $sort=true, $unique_field){
        //取出排序源
        $field_arr_key = array_column($data, $unique_field);
        $field_arr_val = array_column($data, $field);

        $source_arr = [];
        foreach ($field_arr_key as $key=>$val){
            $source_arr[$val] = $field_arr_val[$key];
        }

        //排序
        if($sort)arsort($source_arr);
        else asort($source_arr) ;
        //重组数据
        $new_arr = [];
        foreach ($source_arr as $k=>$v){
            foreach ($data as $a=>$b){
                if($k == $b[$unique_field]){
                    array_push($new_arr, $b);
                }
            }
        }
        return $new_arr;
    }
    /**
     * desc：日期换算为 今天 昨天 2天前 一周前 一个月前 一年前
     * author：wh
     * @param $date 时间戳
     */
    static function date_revert_to_string_type($date){
        $date = $date*1;
        $arr = [
            0=>'今天',
            1=>'昨天',
            2=>'前天',
            7=>'一周前',
            30=>'一个月前',
            365=>'一年前',
            -1=>'很久以前',
        ];
        //今天
        $today = strtotime(date('Y-m-d'));
        if(($date-$today)>=0){
            return $arr[0];
        }else if(($date-$today)<0 && ($today-$date)<=86400){
            return $arr[1];
        }else if(($date-$today)<0 && ($today-$date)<=86400*2){
            return $arr[2];
        }else if(($date-$today)<0 && ($today-$date)<=86400*7){
            return $arr[7];
        }else if(($date-$today)<0 && ($today-$date)<=86400*30){
            return $arr[30];
        }else if(($date-$today)<0 && ($today-$date)<=86400*365){
            return $arr[365];
        }
        return $arr[-1];
    }
    /**
     * desc：返回由二维数组的其中两个字段(键)组成的一维数组
     * author：wh
     */
    static function key_val_arr($data, $key, $key2){
        $arr = array_column($data, $key);
        $arr2 = array_column($data, $key2);
        $tmp = [];
        foreach ($arr as $k=>$v){
            $tmp[$v]=$arr2[$k];
        }
        return $tmp;
    }
    /**
     * desc：查找二维数组中某一条数据
     * author：wh
     */
    static function arr_data_one($data, $field, $val){
        foreach ($data as $k=>$v){
            if($v[$field] == $val){
                return $v;
            }
        }
        return false;
    }

    /**
     * description：unicode编码
     * author：wh
     * @param $str
     * @return string
     */
    static function unicodeEncode($str){
        //split word
        preg_match_all('/./u',$str,$matches);

        $unicodeStr = "";
        foreach($matches[0] as $m){
            //拼接
            $unicodeStr .= "&#".base_convert(bin2hex(iconv('UTF-8',"UCS-4",$m)),16,10);
        }
        return $unicodeStr;
    }
    /**
     * desc：返回unicode解码后的中文
     *   $name = '\u65b0\u6d6a\u5fae\u535a';
     *   $data = unicodeDecode($name); //输出新浪微博
     * 配合 unicodeDecode 使用
     * author：wh
     * @param $match
     * @return string
     */
    static function replace_unicode_escape_sequence($match) {
        return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
    }

    /**
     * desc：中文被unicode编码后了的数据，解码出中文
     * 使用Unicode解码
     * author：wh
     * @param $data
     * @return null|string|string[]
     */
    static function unicodeDecode($data){
        return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $data);
    }

    /**
     * desc：返回年中第几天
     * author：wh
     * @param $month
     * @param $day
     * @param $year
     * @return false|string
     */
    static function get_day_Year($month, $day,$year){

        $day = date('z',mktime(0,0,0,$month,$day,$year));

        return $day;
    }

    /**
     * desc：返回11位数编号，末位一位表示年份,第4位开始3位数表示几月几日
     * eg：28431855779；9=2019，318=11月15日
     * 注意：随机数都可能会重复
     * author：wh
     * @return string
     * @throws \Exception
     */
    static function create_number(){
        $day = date('z',mktime(0,0,0,date('m'),date('d'),date('Y')));
        $day = sprintf('%03d',$day);
        $sp = str_split( date('y') );

        $rand = random_int(1000000,9999999);
        $str1 = substr($rand,0,3);
        $str2 = substr($rand,3,strlen($rand));

        return ($str1.$day.$str2.$sp[1]);
    }
    
    /**
     * [create_batch 生成交易批次号]
     * @Author QiaoWeiWei
     * @Date   2019-12-26
     * @param  [type]     $merchant_basic_id [商户id]
     * @param  [type]     $last_batch        [最新的批次号]
     * @return [type]                        [description]
     */
    static function create_batch($merchant_basic_id,$last_batch){
        $basic_no = str_pad($merchant_basic_id, 4,'0',STR_PAD_LEFT);
        if(empty($last_batch) || substr($last_batch, 0,8) != date('Ymd')){
            $batch = date('Ymd').'-'.$basic_no.'01';
        }else{
            $batch_no = (int)substr($last_batch, -2); //批次号 末两位字符串数字转换为整数
            $batch = date('Ymd').'-'.$basic_no.str_pad(($batch_no+1),2,'0',STR_PAD_LEFT);
        }
        return $batch;
    }

    /**
     * [create_serial_number 生成流水号]
     * @Author QiaoWeiWei
     * @Date   2019-11-27
     * @return [type]     [description]
     */
    static function create_serial_number(){
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $micro_second = substr($microtime, -3);
        $rand_str = self::rand_str(6,3);
        return date('YmdHis').$micro_second.$rand_str;
    }

    /**
     * [create_order_number 生成单据编号 22位]
     * @Author wh
     * @Date   2019-11-27
     * @return [type]     [description]
     */
    static function create_order_number(){
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $micro_second = substr($microtime, -3);
        $rand_str = self::rand_str(8,3);
        return time().$micro_second.$rand_str;
    }

    /**
     * [create_order_number 生成单据编号 25位]
     * @Author wh
     * @Date   2019-11-27
     * @return [type]     [description]
     */
    static function create_order_no(){
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $micro_second = substr($microtime, -3);
        $rand_str = self::rand_str(8,3);
        return date('YmdHis').$micro_second.$rand_str;
    }

    /**
     * [create_invoice_apply_number 生成发票申请编号]
     * @Author QiaoWeiWei
     * @Date   2019-12-23
     * @return [type]     [description]
     */
    static function create_invoice_apply_number(){
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $rand_str = self::rand_str(4,3);
        return $microtime.$rand_str;
    }

    static function get_now_date(){
        return date('Y-m-d H:i:s');
    }
    static function get_init_time(){
        return '1970-01-01 10:00:00';
    }

    /**
     * cURL 网络链接库
     * GET
     * author：wh
     * @param $url
     * @return bool|int|string
     */
    static function curl_get($url, $timeout=60){

        $header = array(
            'Accept: application/json',
        );
        $curl = curl_init();
        //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        //curl_setopt($curl, CURLOPT_SSLVERSION, 3);
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        // 超时设置,以秒为单位
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //print "Error: ".curl_errno($curl).'-' . curl_error($curl);
            //返回错误码
            return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code'=>200, 'msg'=>'ok', 'data'=>$data];
        }
    }

    /**
     * cURL 网络链接库
     * POST
     * author：wh
     * @param $url 是请求的链接
     * @param $postdata 传输的数据，数组格式
     * @return bool|int|string
     */
    static function curl_post( $url, $postdata , $timeout=60) {

        $header = array(
            'Accept: application/json',
        );

        //初始化
        $curl = curl_init();
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        // 超时设置
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE );
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE );

        //设置post方式提交
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //返回错误码
            return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code'=>200, 'msg'=>'ok', 'data'=>$data];
        }
    }

    /**
     * [请求Java接口]
     * 思考：1、Java端如果用文件流的方式去获取数据，调用此方法
     * @param $url
     * @param $postdata
     * @param int $timeout
     * @return array
     * @link
     * @example
     * @see
     */
    static function java_curl_post_file_request( $url, $postdata , $timeout=60) {

        $header = array(
            'Accept: application/json',
        );

        //初始化
        $curl = curl_init();
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        // 超时设置
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE );
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE );

        //设置post方式提交
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //返回错误码
            return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code'=>200, 'msg'=>'ok', 'data'=>$data];
        }
    }

    /**
     * desc：将字符串按大写字母分割为数组
     * author：wh
     * @param $string
     * @return array[]|false|string[]
     */
    static function letterByUpperSplit($string){
        return preg_split("/(?=[A-Z])/", $string);
    }

    /**
     * desc：将下划线命名 转换为 驼峰式命名
     * author：wh
     * @param string $str
     * @param bool $ucfirst
     * @return mixed|string
     */
    static function convertUnderLine(string $str, $ucfirst = true){
        $str = ucwords(str_replace('_', ' ', $str));
        $str = str_replace(' ','',lcfirst($str));
        return $ucfirst ? ucfirst($str) : $str;
    }

    /**
     * desc：将驼峰式命名 转换为 下划线命名
     * author：wh
     * @param $str
     * @return string
     */
    static function convertLineUnder($str){
        return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $str));
    }


    /**
     * desc：获取数字的英文单词
     * author：wh
     * @param null $key
     * @return array|mixed
     */
    static function getNumEn($key=null){
        $num_en = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
        if(!is_null($key)) return $num_en[$key];
        return $num_en;
    }


    /**
     * @param $filePath //下载文件的路径
     * @param $fileName //下载文件的名称
     * @param int $readBuffer //分段下载 每次下载的字节数 默认1024bytes
     * @param array $allowExt //允许下载的文件类型
     * @return void
     */
    static function downloadFile($filePath, $fileName='', $readBuffer = 1024, $allowExt = ['jpeg', 'jpg', 'peg', 'gif', 'zip', 'rar', 'apk'])
    {
        //检测下载文件是否存在 并且可读
        if (!is_file($filePath) && !is_readable($filePath)) {
            return false;
        }
        //检测文件类型是否允许下载
        $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        if (!in_array($ext, $allowExt)) {
            return false;
        }
        //设置头信息
        //声明浏览器输出的是字节流
        header('Content-Type: application/octet-stream');
        //声明浏览器返回大小是按字节进行计算
        header('Accept-Ranges:bytes');
        //告诉浏览器文件的总大小
        $fileSize = filesize($filePath);//坑 filesize 如果超过2G 低版本php会返回负数
        header('Content-Length:' . $fileSize); //注意是'Content-Length:' 非Accept-Length
        //声明下载文件的名称
        header('Content-Disposition:attachment;filename=' . ($fileName?$fileName:basename($filePath)));//声明作为附件处理和下载后文件的名称
        //获取文件内容
        $handle = fopen($filePath, 'rb');//二进制文件用‘rb’模式读取
        while (!feof($handle)) { //循环到文件末尾 规定每次读取（向浏览器输出为$readBuffer设置的字节数）
            echo fread($handle, $readBuffer);
        }
        fclose($handle);//关闭文件句柄
        exit;
    }

    /**
     * 文件直接下载[支持本地文件和远程文件]
     *   sys_download_file('web服务器中的文件地址', 'test.jpg');
     *   sys_download_file('远程文件地址', 'test.jpg', true);
     *
     * @param  string  $path     文件地址：针对当前服务器环境的相对或绝对地址
     * @param  string  $name     下载后的文件名（包含扩展名）
     * @param  boolean $isRemote 是否是远程文件（通过 url 无法获取文件扩展名的必传参数 name）
     * @param  string  $proxy    代理，适用于需要使用代理才能访问外网资源的情况
     * @return true|false 下载结果
     */
    static function downloadUrl($path, $name = null, $isRemote = false, $contentType='binary_system', $proxy = '') {
        $fileRelativePath = $path;
        $savedFileName = $name;
        if (!$savedFileName) {
            $file = pathinfo($path);
            if (!empty($file['extension'])) {
                $savedFileName = $file['basename'];
            } else {
                throw new FileDownloadException("Extension get failed, parameter 'name' is required!");
            }
        }

        // 如果是远程文件，先下载到本地
        if ($isRemote) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $path);
            if ($proxy != '') {
                curl_setopt($ch, CURLOPT_PROXY, $proxy);
            }
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 50);
            $fileContent = curl_exec($ch);
            curl_close($ch);

            // 写入临时文件
            $fileRelativePath = tempnam(sys_get_temp_dir(), 'DL');
            $fp = @fopen($fileRelativePath, 'w+');
            fwrite($fp, $fileContent);
        }

        $contentTypeArr = [
            //二进制
            'binary_system' => 'application/octet-stream',
            //apk安装包
            'apk' => 'application/vnd.android.package-archive',
        ];
        // 执行下载
        if (is_file($fileRelativePath)) {
            //传输类型
            header('Content-Description: File Transfer');
            //Content-Type
            if(empty($contentTypeArr[$contentType])){
                //默认二进制格式
                header('Content-Type: application/octet-stream');
            }else{
                //根据参数指定下载格式
                header('Content-Type: '.$contentTypeArr[$contentType]);
            }
            //文件大小
            header('Content-Length:' . filesize($fileRelativePath));

            if (preg_match('/MSIE/', $_SERVER['HTTP_USER_AGENT'])) { // for IE
                header('Content-Disposition: attachment; filename="' . rawurlencode($savedFileName) . '"');
            } else {
                header('Content-Disposition: attachment; filename="' . $savedFileName . '"');
            }
            readfile($fileRelativePath);
            if ($isRemote) {
                unlink($fileRelativePath); // 删除下载远程文件时对应的临时文件
            }
            exit;
        } else {
            throw new FileDownloadException('Invalid file: ' . $fileRelativePath);
        }

    }
    /**
     * desc：是否是url
     * author：wh
     * @param $v
     * @return bool
     */
    static function is_url($v){
        $pattern="#(http|https)://(.*\.)?.*\..*#i";
        return preg_match($pattern,$v)?true:false;
    }

    /**
     * [本地文件]
     * desc：根据一个完整的url地址，获取资源文件大小
     * author：wh
     * @param $url [一个可访问的站内合法地址。eg： http://39.101.214.157/static/upload/d4e271103e4536db/1d9f980651cdbc35.mp4 ]
     * @param string $domain 域名结束不含 "/" 符号
     * @return bool|false|int 文件大小
     */
    static function getFileSize($url, $domain=''){
        $str = str_replace($domain?$domain:request()->domain(), '', $url);
        $mac_url = config('app.ROOT_PATH_PRO').'public'.$str;
        if(!is_file($mac_url)) return false;
        return filesize($mac_url);
    }


    /**
     * desc：组装$_FILES原始数据,便于业务逻辑操作
     * author：wh
     * @param $name
     * @return array
     */
    static function groupFile($name){
        $files = $_FILES[$name];
        $tmp = [];
        for ($j=0; $j<count($files['name']); $j++){
            $t['name'] = $files['name'][$j];
            $t['type'] = $files['type'][$j];
            $t['tmp_name'] = $files['tmp_name'][$j];
            $t['error'] = $files['error'][$j];
            $t['size'] = $files['size'][$j];
            $tmp[] = $t;
        }
        return $tmp;
    }

    /**
     * [获取协议类型]
     *
     * @return string
     * @link
     * @example
     * @see
     */
    static function getHttpType(){
        return input('server.REQUEST_SCHEME') . '://' . input('server.SERVER_NAME');
    }

    /**
     * [是否全部大写]
     *
     * @param $str
     * @return bool
     * @example
     * @see
     * @link
     */
    static function isUpper($str){

        return preg_match('/^[A-Z]+$/', $str)?true:false;
    }

    /**
     * [是否全部小写]
     *
     * @param $str
     * @return bool
     * @example
     * @see
     * @link
     */
    static function isLower($str){
        return preg_match('/^[a-z]+$/', $str)?true:false;
    }

    /**
     * 将字符串分割为数组
     * @param  string $str 字符串
     * @return array       分割得到的数组
     */
    static function mbStrSplit($str){
        return preg_split('/(?<!^)(?!$)/u', $str);
    }
}